package org.xx.armory.spring5.security;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.xx.armory.commons.ForLogging;

import java.nio.CharBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

import static java.nio.charset.StandardCharsets.UTF_8;

public class SimplePasswordEncoder
        implements PasswordEncoder {
    @ForLogging
    private final Logger logger = LoggerFactory.getLogger(SimplePasswordEncoder.class);

    @Value("${armory.security.algorithm:SHA-1}")
    private String algorithm;

    @Value("${armory.security.salt:abc}")
    private String salt;

    CharSequence addSalt(
            CharSequence src
    ) {
        if (src != null && src.length() != 0) {
            final var p = src.length() > 6 ? src.length() / 2 - 2 : 1;
            return src.subSequence(0, p) + this.salt + src.subSequence(p - 1, src.length());
        } else {
            return this.salt;
        }
    }

    /**
     * 对口令进行扰动和哈希。
     *
     * @param src
     *         原口令。
     * @return 扰动和哈希之后的口令。
     */
    private String hashPassword(
            CharSequence src
    ) {
        // 扰动。
        final var cb = CharBuffer.wrap(addSalt(src));

        // 哈希。
        try {
            final var md = MessageDigest.getInstance(this.algorithm);
            md.update(UTF_8.encode(cb));

            // 按照Base64编码为字符串。
            return Base64.getEncoder().encodeToString(md.digest());
        } catch (NoSuchAlgorithmException ex) {
            throw new RuntimeException("Cannot create MessageDigest(algorithm=" + this.algorithm + ")", ex);
        }

    }

    @Override
    public String encode(
            CharSequence password
    ) {
        return hashPassword(password);
    }

    @Override
    public boolean matches(
            CharSequence password,
            String hashed
    ) {
        return hashPassword(password).equalsIgnoreCase(hashed);
    }
}
